package com.zgis.module.db.service;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.CommonMapperX;
import com.zgis.module.bas.util.CxMapUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 表数据缓存服务
 * 负责将指定表的数据加载到 Redis 中
 *
 * @author yudao
 */
@Slf4j
@Service
public class TableCacheService {

    @Resource
    private CommonMapperX commonMapperX;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 需要缓存的表配置（使用 LinkedHashMap 保持顺序）
     * Key: Redis缓存键名, Value: 数据库表名
     * 注意：CxFld 必须放在第一位，因为其他表需要用到它的字段定义
     */
    private static final Map<String, String> TABLE_CACHE_CONFIG = new LinkedHashMap<>();

    static {
        TABLE_CACHE_CONFIG.put("CxFld", "cx_fld");
        TABLE_CACHE_CONFIG.put("CxEntity", "cx_entity");
        TABLE_CACHE_CONFIG.put("CxSqlExp", "cx_sqlexp");
        TABLE_CACHE_CONFIG.put("CxSqlPro", "cx_sqlpro");
        TABLE_CACHE_CONFIG.put("CxFldValue", "cx_fldvalue");
        TABLE_CACHE_CONFIG.put("CxSyscfg", "cx_syscfg");
        TABLE_CACHE_CONFIG.put("CxUserSql", "cx_usersql");
    }

    private static final String FORMATTED_KEY_SUFFIX = "_formatted";
    private static final String CX_FLD_TABLE = "cx_fld";
    private static final Map<String, Object> EMPTY_RESULT = createEmptyResult();

    private static Map<String, Object> createEmptyResult() {
        Map<String, Object> result = new HashMap<>(3);
        result.put("data", Collections.emptyList());
        result.put("cols", "");
        result.put("types", "");
        return result;
    }

    /**
     * 加载所有配置的表数据到 Redis
     * 会先加载 cx_fld 表，然后使用其字段定义信息加载其他表
     */
    public void loadAllTablesToCache() {
        log.info("[TableCacheService] 开始加载表数据到 Redis");
        long startTime = System.currentTimeMillis();
        int successCount = 0;
        int failCount = 0;

        List<Map<String, Object>> cxFldMapList = null;

        for (Map.Entry<String, String> entry : TABLE_CACHE_CONFIG.entrySet()) {
            String cacheKey = entry.getKey();
            String tableName = entry.getValue();

            try {
                if (CX_FLD_TABLE.equals(tableName)) {
                    cxFldMapList = loadCxFldTable(cacheKey, tableName);
                    successCount++;
                } else {
                    if (cxFldMapList == null) {
                        log.error("[TableCacheService] cx_fld 表未加载，无法加载表 {}", tableName);
                        failCount++;
                        continue;
                    }
                    loadTableToCache(cacheKey, tableName, cxFldMapList);
                    successCount++;
                }
            } catch (Exception e) {
                failCount++;
                log.error("[TableCacheService] 加载表 {} 失败", tableName, e);
            }
        }

        long endTime = System.currentTimeMillis();
        log.info("[TableCacheService] 表数据加载完成，成功: {}, 失败: {}, 耗时: {}ms",
                successCount, failCount, (endTime - startTime));
    }

    /**
     * 加载 cx_fld 表数据到 Redis，并返回数据用于其他表
     *
     * @param cacheKey  Redis 缓存键名
     * @param tableName 数据库表名
     * @return cx_fld 表的数据列表
     */
    private List<Map<String, Object>> loadCxFldTable(String cacheKey, String tableName) {
        log.info("[TableCacheService] 开始加载 {} 表到 Redis，缓存键: {}", tableName, cacheKey);

        List<Map<String, Object>> tableData = commonMapperX.queryAll(tableName, "*", null);

        if (CollUtil.isEmpty(tableData)) {
            log.warn("[TableCacheService] 表 {} 没有数据", tableName);
            cacheEmptyData(cacheKey);
            return Collections.emptyList();
        }

        // 存储原始数据
        redisTemplate.opsForValue().set(cacheKey, tableData);

        // cx_fld 表使用自身的字段类型信息
        Map<String, String> typesMap = getTypesFromMapList(tableName, tableData);
        Map<String, Object> formattedData = CxMapUtil.toDataColsTypes(tableData, typesMap);

        // 存储格式化数据
        redisTemplate.opsForValue().set(cacheKey + FORMATTED_KEY_SUFFIX, formattedData);
        log.info("[TableCacheService] 表 {} 加载成功，共 {} 条数据", tableName, tableData.size());

        return tableData;
    }

    /**
     * 加载单个表数据到 Redis
     *
     * @param cacheKey     Redis 缓存键名
     * @param tableName    数据库表名
     * @param cxFldMapList 字段定义 Map 列表
     */
    private void loadTableToCache(String cacheKey, String tableName, List<Map<String, Object>> cxFldMapList) {
        log.info("[TableCacheService] 开始加载表 {} 到 Redis，缓存键: {}", tableName, cacheKey);

        List<Map<String, Object>> tableData = commonMapperX.queryAll(tableName, "*", null);

        if (CollUtil.isEmpty(tableData)) {
            log.warn("[TableCacheService] 表 {} 没有数据", tableName);
            cacheEmptyData(cacheKey);
            return;
        }

        // 存储原始数据
        redisTemplate.opsForValue().set(cacheKey, tableData);

        // 从 cx_fld 中获取该表的字段类型信息
        Map<String, String> typesMap = getTypesFromMapList(tableName, cxFldMapList);
        Map<String, Object> formattedData = CxMapUtil.toDataColsTypes(tableData, typesMap);

        // 存储格式化数据
        redisTemplate.opsForValue().set(cacheKey + FORMATTED_KEY_SUFFIX, formattedData);
        log.info("[TableCacheService] 表 {} 加载成功，共 {} 条数据", tableName, tableData.size());
    }

    /**
     * 缓存空数据
     *
     * @param cacheKey Redis 缓存键名
     */
    private void cacheEmptyData(String cacheKey) {
        redisTemplate.opsForValue().set(cacheKey, Collections.emptyList());
        redisTemplate.opsForValue().set(cacheKey + FORMATTED_KEY_SUFFIX, EMPTY_RESULT);
    }

    /**
     * 从 Map 列表中获取指定表的字段类型映射
     *
     * @param tableName    表名
     * @param cxFldMapList 字段定义 Map 列表
     * @return 字段类型映射 Map<字段名, 类型值>
     */
    private Map<String, String> getTypesFromMapList(String tableName, List<Map<String, Object>> cxFldMapList) {
        return cxFldMapList.stream()
                .filter(fldMap -> tableName.equals(fldMap.get("tabname")))
                .collect(Collectors.toMap(
                        fldMap -> (String) fldMap.get("colname"),
                        CxMapUtil::getTypeVal,
                        (existing, replacement) -> existing // 如果有重复的key，保留第一个
                ));
    }

    /**
     * 重新加载所有表数据
     */
    public void reloadAllTables() {
        log.info("[TableCacheService] 开始重新加载所有表数据");
        // 先清除旧缓存
        clearAllCache();
        // 重新加载
        loadAllTablesToCache();
    }

    /**
     * 清除所有表缓存
     */
    public void clearAllCache() {
        log.info("[TableCacheService] 开始清除所有表缓存");
        
        List<String> keysToDelete = TABLE_CACHE_CONFIG.keySet().stream()
                .flatMap(key -> Arrays.asList(key, key + FORMATTED_KEY_SUFFIX).stream())
                .collect(Collectors.toList());
        
        try {
            redisTemplate.delete(keysToDelete);
            log.info("[TableCacheService] 成功清除 {} 个缓存键", keysToDelete.size());
        } catch (Exception e) {
            log.error("[TableCacheService] 批量清除缓存失败", e);
        }
    }

    /**
     * 获取缓存的表数据（原始格式）
     *
     * @param cacheKey Redis 缓存键名
     * @return 表数据列表
     */
    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> getCachedTableData(String cacheKey) {
        try {
            return (List<Map<String, Object>>) redisTemplate.opsForValue().get(cacheKey);
        } catch (Exception e) {
            log.error("[TableCacheService] 获取缓存 {} 失败", cacheKey, e);
            return null;
        }
    }

    /**
     * 获取缓存的表数据（JSON格式）
     *
     * @param cacheKey Redis 缓存键名
     * @return 包含 data、cols、types 的 Map
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> getCachedTableDataAsJson(String cacheKey) {
        try {
            return (Map<String, Object>) redisTemplate.opsForValue().get(cacheKey + FORMATTED_KEY_SUFFIX);
        } catch (Exception e) {
            log.error("[TableCacheService] 获取格式化缓存 {} 失败", cacheKey + FORMATTED_KEY_SUFFIX, e);
            return null;
        }
    }

    /**
     * 获取表缓存配置
     *
     * @return 表缓存配置 Map<缓存键名, 表名>
     */
    public static Map<String, String> getTableCacheConfig() {
        return TABLE_CACHE_CONFIG;
    }
}
